// This file is part of Notepad4.
// See License.txt for details about distribution and modification.
//! Lexer for JSON, JSON5.

#include <cassert>
#include <cstring>

#include <string>
#include <string_view>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "CharacterSet.h"
#include "LexerModule.h"

using namespace Lexilla;

namespace {

enum {
	JsonChar_None = 0,
	JsonChar_BraceOpen = 1,
	JsonChar_BraceClose = 2,
	JsonChar_WordStart = 3,
	JsonChar_Dot = 4,
	JsonChar_Slash = 5,
	JsonChar_Ignore = 6,

	JsonMask_Number = 1 << 3,
	JsonMask_Identifier = 1 << 4,
};

constexpr int GetStringQuote(int state) noexcept {
	static_assert(SCE_JSON_STRING_DQ*5 + 4 == '\"');
	static_assert(SCE_JSON_STRING_SQ*5 + 4 == '\'');
	return state*5 + 4;
}

bool IsJsonProperty(LexAccessor &styler, Sci_PositionU startPos, uint8_t chNext) noexcept {
	if (chNext == ':') {
		return true;
	}
	if (chNext > '\0' && chNext <= ' ') {
		do {
			chNext = styler[++startPos];
			if (chNext == ':') {
				return true;
			}
		} while (chNext > '\0' && chNext <= ' ');
	}
	return false;
}

void ColouriseJSONDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList keywordLists, Accessor &styler) {
	const bool fold = styler.GetPropertyBool("fold");

	// JSON5 line continuation
	bool lineContinuation = false;
	bool atLineStart = true;
	int state = initStyle;
	uint8_t chNext = styler[startPos];
	styler.StartAt(startPos);
	styler.StartSegment(startPos);

	Sci_Line lineCurrent = styler.GetLine(startPos);
	int levelCurrent = SC_FOLDLEVELBASE;
	if (lineCurrent > 0) {
		levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
		if (state == SCE_JSON_STRING_DQ || state == SCE_JSON_STRING_SQ) {
			const Sci_Position pos = styler.LineEnd(lineCurrent - 1) - 1;
			if (pos > 0 && styler[pos] == '\\') {
				lineContinuation = true;
			}
		}
	}
	int levelNext = levelCurrent;

	constexpr int MaxLexWordLength = 9; // Infinity
	char buf[MaxLexWordLength + 1];
	int wordLen = 0;

	assert(startPos == static_cast<Sci_PositionU>(styler.LineStart(lineCurrent)));
	Sci_PositionU lineStartNext = styler.LineStart(lineCurrent + 1);
	const Sci_PositionU endPos = startPos + lengthDoc;

	// see GenerateJsonCharClass() in scripts/GenerateCharTable.py
	static constexpr uint8_t kJsonCharClass[256] = {
//++Autogenerated -- start of section automatically generated
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 102, 198, 102, 179, 102, 102, 230, 102, 102, 102, 110, 102, 110, 108, 101,
158, 158, 158, 158, 158, 158, 158, 158, 158, 158, 102, 102, 102, 102, 102, 102,
102, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187,
187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 97, 179, 98, 102, 187,
102, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187,
187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 97, 102, 98, 102, 102,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179,
//--Autogenerated -- end of section automatically generated
	};

	while (startPos < endPos) {
		const uint8_t ch = chNext;
		const Sci_PositionU currentPos = startPos++;
		chNext = styler[startPos];

		switch (state) {
		case SCE_JSON_OPERATOR:
			styler.ColorTo(currentPos, state);
			state = SCE_JSON_DEFAULT;
			break;

		case SCE_JSON_NUMBER:
			if (!(kJsonCharClass[ch] & JsonMask_Number)) {
				styler.ColorTo(currentPos, state);
				state = SCE_JSON_DEFAULT;
			}
			break;

		case SCE_JSON_IDENTIFIER:
			if (!(kJsonCharClass[ch] & JsonMask_Identifier)) {
				buf[wordLen] = '\0';
				if (keywordLists[0].InList(buf)) {
					state = SCE_JSON_KEYWORD;
				} else if (ch == ':' || (ch <= ' ' && IsJsonProperty(styler, startPos, chNext))) {
					state = SCE_JSON_PROPERTYNAME;
				}
				styler.ColorTo(currentPos, state);
				state = SCE_JSON_DEFAULT;
			} else if (wordLen < MaxLexWordLength) {
				buf[wordLen++] = static_cast<char>(ch);
			}
			break;

		case SCE_JSON_STRING_DQ:
		case SCE_JSON_STRING_SQ:
			if (atLineStart) {
				if (lineContinuation) {
					lineContinuation = false;
				} else {
					styler.ColorTo(currentPos, state);
					state = SCE_JSON_DEFAULT;
				}
			} else if (ch == '\\') {
				if (IsEOLChar(chNext)) {
					lineContinuation = true;
				} else {
					// highlight any character as escape sequence
					styler.ColorTo(currentPos, state);
					++startPos;
					if (chNext == 'u' || chNext == 'x') {
						int count = (chNext == 'x') ? 2 : 4;
						do {
							chNext = styler[startPos];
							if (!IsXDigit(chNext)) {
								break;
							}
							++startPos;
							--count;
						} while (count);
					}

					chNext = styler[startPos];
					styler.ColorTo(startPos, SCE_JSON_ESCAPECHAR);
					continue;
				}
			} else if (ch == GetStringQuote(state)) {
				if (chNext == ':' || IsJsonProperty(styler, startPos, chNext)) {
					state = SCE_JSON_PROPERTYNAME;
				}
				styler.ColorTo(startPos, state);
				state = SCE_JSON_DEFAULT;
				continue;
			}
			break;

		case SCE_JSON_LINECOMMENT:
			if (atLineStart) {
				styler.ColorTo(currentPos, state);
				state = SCE_JSON_DEFAULT;
			}
			break;

		case SCE_JSON_BLOCKCOMMENT:
			if (ch == '*' && chNext == '/') {
				startPos++;
				chNext = styler[startPos];
				styler.ColorTo(startPos, state);
				state = SCE_JSON_DEFAULT;
				levelNext--;
				continue;
			}
			break;
		}

		if (state == SCE_JSON_DEFAULT) {
			const int nextState = kJsonCharClass[ch];
			const int charClass = nextState & 7;
			if (charClass) {
				styler.ColorTo(currentPos, state);
				state = nextState >> 5;
				switch (charClass) {
				case JsonChar_BraceOpen:
					levelNext++;
					break;
				case JsonChar_BraceClose:
					levelNext--;
					break;
				case JsonChar_WordStart:
					buf[0] = static_cast<char>(ch);
					wordLen = 1;
					break;
				case JsonChar_Dot:
					if (IsADigit(chNext)) {
						state = SCE_JSON_NUMBER;
					}
					break;
				case JsonChar_Slash:
					if (chNext == '/') {
						state = SCE_JSON_LINECOMMENT;
					} else if (chNext == '*') {
						state = SCE_JSON_BLOCKCOMMENT;
						levelNext++;
						startPos++;
						chNext = styler[startPos];
					}
					break;
				default:
					break;
				}
			}
		}

		if (styler.IsLeadByte(ch)) {
			// ignore trail byte in DBCS character
			startPos++;
			chNext = styler[startPos];
		}

		atLineStart = startPos == lineStartNext;
		if (atLineStart) {
			if (fold) {
				levelNext = sci::max(levelNext, SC_FOLDLEVELBASE);
				const int levelUse = levelCurrent;
				int lev = levelUse | (levelNext << 16);
				if (levelUse < levelNext) {
					lev |= SC_FOLDLEVELHEADERFLAG;
				}
				styler.SetLevel(lineCurrent, lev);
				levelCurrent = levelNext;
			}
			lineCurrent++;
			lineStartNext = styler.LineStart(lineCurrent + 1);
		}
	}

	// Colourise remaining document
	styler.ColorTo(endPos, state);
}

}

extern const LexerModule lmJSON(SCLEX_JSON, ColouriseJSONDoc, "json");
